/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002-2006 * Sleepycat Software. All rights reserved. * * $Id: ScavengerFileReader.java,v 1.1 2006/05/06 09:00:00 ckaestne Exp $ */ package com.sleepycat.je.log; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Set; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.log.entry.LogEntry; import com.sleepycat.je.utilint.DbLsn; /** * A ScavengerFileReader reads the log backwards. If it encounters a checksum * error, it goes to the start of that log file and reads forward until it * encounters a checksum error. It then continues the reading backwards in the * log. * * The caller may set "dumpCorruptedBounds" to true if information about the * start and finish of the corrupted portion should be displayed on stderr. * * The caller is expected to implement processEntryCallback. This method is * called once for each entry that the ScavengerFileReader finds in the log. */ abstract public class ScavengerFileReader extends FileReader { /* A Set of the entry type numbers that this FileReader should dump. */ private Set targetEntryTypes; private int readBufferSize; /* True if reader should write corrupted boundaries to System.err. */ private boolean dumpCorruptedBounds; /** * Create this reader to start at a given LSN. */ public ScavengerFileReader(EnvironmentImpl env, int readBufferSize, long startLsn, long finishLsn, long endOfFileLsn) throws IOException, DatabaseException { super(env, readBufferSize, false, startLsn, null, // single file number endOfFileLsn, finishLsn); this.readBufferSize = readBufferSize; /* * Indicate that a checksum error should not shutdown the whole * environment. */ anticipateChecksumErrors = true; targetEntryTypes = new HashSet(); dumpCorruptedBounds = false; } /** * Set to true if corrupted boundaries should be dumped to stderr. */ public void setDumpCorruptedBounds(boolean dumpCorruptedBounds) { this.dumpCorruptedBounds = dumpCorruptedBounds; } /** * Tell the reader that we are interested in these kind of entries. */ public void setTargetType(LogEntryType type) { targetEntryTypes.add(new Byte(type.getTypeNum())); } /* * For each entry that is selected, just call processEntryCallback. */ protected boolean processEntry(ByteBuffer entryBuffer) throws DatabaseException { LogEntryType lastEntryType = LogEntryType.findType(currentEntryTypeNum, currentEntryTypeVersion); LogEntry entry = lastEntryType.getSharedLogEntry(); entry.readEntry(entryBuffer, currentEntrySize, currentEntryTypeVersion, true); processEntryCallback(entry, lastEntryType); return true; } /* * Method overriden by the caller. Each entry of the types selected * is passed to this method. */ abstract protected void processEntryCallback(LogEntry entry, LogEntryType entryType) throws DatabaseException; /* * Read the next entry. If a checksum exception is encountered, attempt * to find the other side of the corrupted area and try to re-read this * file. */ public boolean readNextEntry() throws DatabaseException, IOException { long saveCurrentEntryOffset = currentEntryOffset; try { return super.readNextEntry(); } catch (DbChecksumException DCE) { resyncReader(DbLsn.makeLsn(readBufferFileNum, saveCurrentEntryOffset), dumpCorruptedBounds); return super.readNextEntry(); } } /* * A checksum error has been encountered. Go to the start of this log file * and read forward until the lower side of the corrupted area has been * found. */ protected boolean resyncReader(long nextGoodRecordPostCorruption, boolean showCorruptedBounds) throws DatabaseException, IOException { LastFileReader reader = null; long tryReadBufferFileNum = DbLsn.getFileNumber(nextGoodRecordPostCorruption); while (tryReadBufferFileNum >= 0) { try { reader = new LastFileReader(env, readBufferSize, new Long(tryReadBufferFileNum)); break; } catch (DbChecksumException DCE) { /* * We found a checksum error reading the header of this file * so skip to a completely earlier file. */ tryReadBufferFileNum--; continue; } } boolean switchedFiles = tryReadBufferFileNum != DbLsn.getFileNumber(nextGoodRecordPostCorruption); if (!switchedFiles) { /* * This reader will not throw an exception if a checksum error is * hit -- it will just exit. */ while (reader.readNextEntry()) { } } long lastUsedLsn = reader.getLastValidLsn(); long nextAvailableLsn = reader.getEndOfLog(); if (showCorruptedBounds) { System.err.println("A checksum error was found in the log."); System.err.println ("Corruption begins at LSN:\n " + DbLsn.toString(nextAvailableLsn)); System.err.println ("Last known good record before corruption is at LSN:\n " + DbLsn.toString(lastUsedLsn)); System.err.println ("Next known good record after corruption is at LSN:\n " + DbLsn.toString(nextGoodRecordPostCorruption)); } startLsn = lastUsedLsn; initStartingPosition(nextAvailableLsn, null); if (switchedFiles) { currentEntryPrevOffset = 0; } /* Indicate resync is permitted so don't throw exception. */ return true; } /** * @return true if this reader should process this entry, or just skip * over it. */ protected boolean isTargetEntry(byte logEntryTypeNumber, byte logEntryTypeVersion) { if (targetEntryTypes.size() == 0) { /* We want to dump all entry types. */ return true; } else { return targetEntryTypes.contains(new Byte(logEntryTypeNumber)); } } }